import subprocess
from dataclasses import dataclass, field
from typing import List, Dict

@dataclass
class FFmpegBuilder:
    binary: str = "ffmpeg"
    options: Dict[str, str] = field(default_factory=dict)
    inputs: List['InputBuilder'] = field(default_factory=list)
    outputs: List['OutputBuilder'] = field(default_factory=list)

    def add_input(self, src: 'InputBuilder'):
        self.inputs.append(src)

    def add_output(self, dst: 'OutputBuilder'):
        self.outputs.append(dst)

    def set_binary(self, bin: str):
        self.binary = bin

    def set_flag(self, flag: str):
        self.options[flag] = ""

    def set_option(self, name: str, value: str):
        self.options[name] = value

    def args(self) -> List[str]:
        args = []
        for name, val in self.options.items():
            args.append(f"-{name}")
            if val:
                args.append(val)
        for input in self.inputs:
            args.extend(input.args())
        for output in self.outputs:
            args.extend(output.args())
        return args

    def command(self, ctx) -> subprocess.Popen:
        return subprocess.Popen([self.binary] + self.args(), stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE)

@dataclass
class InputBuilder:
    path: str
    options: Dict[str, List[str]] = field(default_factory=dict)

    def add_option(self, name: str, value: str):
        self.options.setdefault(name, []).append(value)

    def args(self) -> List[str]:
        args = []
        for name, values in self.options.items():
            for val in values:
                args.append(f"-{name}")
                args.append(val)
        args.append("-i")
        args.append(self.path)
        return args

@dataclass
class OutputBuilder:
    path: str
    options: Dict[str, List[str]] = field(default_factory=dict)

    def add_option(self, name: str, value: str):
        self.options.setdefault(name, []).append(value)

    def args(self) -> List[str]:
        args = []
        for name, values in self.options.items():
            for val in values:
                args.append(f"-{name}")
                args.append(val)
        args.append(self.path)
        return args

    def add_metadata(self, stream: str, key: str, value: str):
        opt_val = f"{key.strip()}={value.strip()}"
        if stream:
            self.add_option(f"metadata:{stream}", opt_val)
        else:
            self.add_option("metadata", opt_val)
